
/**
 * AppleIIGo
 * Timing Generator class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include "TimingGenerator.h"

TimingGenerator::TimingGenerator(Motherboard * motherboard,
								 bool isPalTiming) :
Device(motherboard, "TimingGenerator") {
	setPalTiming(isPalTiming);
	
	cpuAccelerationDisableAssertedCount = 0;
	cpuAccelerationDisableForOneFrameCount = 0;
	cpuAccelerationFactor = APPLEIIGS_ACCELERATIONFACTOR;
	
	verticalCountIndex = verticalCountStart;
	
	externalInterrupt = new Interrupt(motherboard, "ExternalInterrupt");
	oneSecondInterrupt = new Interrupt(motherboard, "OneSecondInterrupt");
	quarterSecondInterrupt = new Interrupt(motherboard,
										   "QuarterSecondInterrupt");
	vblInterrupt = new Interrupt(motherboard, "VblInterrupt");
	scanlineInterrupt = new Interrupt(motherboard, "ScanlineInterrupt");
	
	oneSecondInterruptFrameIndex = 0;
	quarterSecondInterruptFrameIndex = 0;
}

void TimingGenerator::onDevicesConnected() {
	cpu = motherboard->getCpu();
	videoRenderer = motherboard->getVideoRenderer();
}

void TimingGenerator::onResetAsserted(bool isRestart) {
	if (isRestart) {
		clockIndex = 0;
		frameClockIndex = 0;
		q3ClockIndex = 0;
		verticalCountIndex = verticalCountStart;
		
		setVgcInterruptRegister(0);
		setSpeedRegister(isRestart ? SPEED_POWERON : 0);
	}
}

void TimingGenerator::setPalTiming(bool value) {
	isPalTiming = value;
	if (isPalTiming)
		verticalCountStart = PAL_VERT_START;
	else
		verticalCountStart = NTSC_VERT_START;

	oneSecondInterruptFrameNum = getFrameRate() + 0.5;
	initScanlineIndexFromVerticalCount();
}

float TimingGenerator::getFrameRate() {
	return isPalTiming ? PAL_FRAMERATE : NTSC_FRAMERATE;
}

UINT32 TimingGenerator::getFrameClockIndex() {
	return (frameClockIndex +
			cpu->getCycleIndex() * HORIZ_NUM * CLOCK14M_CYCLESHORT /
			cpu->getCycleNum());
}

UINT32 TimingGenerator::getFrameClockNum() {
	return CLOCK14M_SCANLINE * (isPalTiming ? PAL_VERT_NUM : NTSC_VERT_NUM);
}

UINT64 TimingGenerator::getQ3ClockIndex() {
	return q3ClockIndex + (cpu->getCycleIndex() << 1);
}

UINT64 TimingGenerator::getClockIndex() {
	return (clockIndex + frameClockIndex +
			cpu->getCycleIndex() * HORIZ_NUM * CLOCK14M_CYCLESHORT /
			cpu->getCycleNum());
}

UINT8 TimingGenerator::readFloatingBus() {
	return videoRenderer->readVideoScanner(getVerticalCount(),
										   getHorizontalCount());
}

bool TimingGenerator::isVbl() {
	return (getVerticalCount() & 0xc0) == 0xc0;
}

void TimingGenerator::updateCpuAcceleration() {
	UINT32 clockNum;
	if (isCpuAccelerationEnabled && !cpuAccelerationDisableForOneFrameCount)
		clockNum = (UINT32) (cpuAccelerationFactor * HORIZ_NUM);
	else
		clockNum = HORIZ_NUM;
	
	if (cpu->getCycleNum())
		cpu->setCycleIndex(cpu->getCycleIndex() * clockNum /
						   cpu->getCycleNum());
	cpu->setCycleNum(clockNum);
}

void TimingGenerator::setCpuAccelerationFactor(double value) {
	cpuAccelerationFactor = value;
	updateCpuAcceleration();
}

double TimingGenerator::getCpuAccelerationFactor() {
	return cpuAccelerationFactor;
}

void TimingGenerator::enableCpuAcceleration(bool value) {
	isCpuAccelerationEnabled = value;
	updateCpuAcceleration();
}

void TimingGenerator::assertDisableCpuAcceleration() {
	cpuAccelerationDisableAssertedCount++;
	updateCpuAcceleration();
}

void TimingGenerator::clearDisableCpuAcceleration() {
	cpuAccelerationDisableAssertedCount--;
	updateCpuAcceleration();
}

void TimingGenerator::assertDisableCpuAccelerationForOneFrame() {
	if (!cpuAccelerationDisableForOneFrameCount)
		assertDisableCpuAcceleration();
	cpuAccelerationDisableForOneFrameCount = 2;
}

void TimingGenerator::clearDisableCpuAccelerationForOneFrame() {
	if (cpuAccelerationDisableForOneFrameCount) {
		cpuAccelerationDisableForOneFrameCount--;
		if (!cpuAccelerationDisableForOneFrameCount)
			clearDisableCpuAcceleration();
	}
}

void TimingGenerator::enableVblInterrupt(bool value) {
	vblInterrupt->enable(value);
}

void TimingGenerator::clearVblInterrupt() {
	vblInterrupt->clear();
}

bool TimingGenerator::isVblInterruptEnabled() {
	return vblInterrupt->isEnabled();
}

bool TimingGenerator::isVblInterruptAsserted() {
	return vblInterrupt->isAsserted();
}

void TimingGenerator::setVgcInterruptRegister(UINT8 value) {
	vgcInterruptRegister = value;
	externalInterrupt->enable(getBit(value, VGCINTERRUPT_EXTERNALINTENABLED));
	scanlineInterrupt->enable(getBit(value, VGCINTERRUPT_SCANLINEINTENABLED));
	oneSecondInterrupt->enable(getBit(value, VGCINTERRUPT_ONESECINTENABLED));
}

UINT8 TimingGenerator::getVgcInterruptRegister() {
	setBit(vgcInterruptRegister, VGCINTERRUPT_EXTERNALINTASSERTED,
		   externalInterrupt->isAsserted());
	setBit(vgcInterruptRegister, VGCINTERRUPT_SCANLINEINTASSERTED,
		   scanlineInterrupt->isAsserted());
	setBit(vgcInterruptRegister, VGCINTERRUPT_ONESECINTASSERTED,
		   oneSecondInterrupt->isAsserted());	
	setBit(vgcInterruptRegister, VGCINTERRUPT_VGCINTASSERTED, 
		   externalInterrupt->isAsserted() ||
		   scanlineInterrupt->isAsserted() ||
		   oneSecondInterrupt->isAsserted());	
	return vgcInterruptRegister;
}

void TimingGenerator::clearVgcInterruptRegister(UINT8 value) {
	if (!getBit(value, VGCINTERRUPTCLEAR_CLEARSCANLINE))
		scanlineInterrupt->clear();
	if (!getBit(value, VGCINTERRUPTCLEAR_CLEARONESEC))
		oneSecondInterrupt->clear();
}

void TimingGenerator::setSpeedRegister(UINT8 value) {
	speedRegister = value;
	enableCpuAcceleration(getBit(value, SPEED_HIGHSPEED));
}

UINT8 TimingGenerator::getSpeedRegister() {
	return speedRegister;
}

UINT8 TimingGenerator::getVerticalCountRegister() {
	return getVerticalCount() >> 1;
}

UINT8 TimingGenerator::getHorizontalCountRegister() {
	return ((getVerticalCount() & 0x1) << 7) | getHorizontalCount();
}

void TimingGenerator::enableQuarterSecondInterrupt(bool value) {
	quarterSecondInterrupt->enable(value);
}

void TimingGenerator::clearQuarterSecondInterrupt() {
	quarterSecondInterrupt->clear();
}

bool TimingGenerator::isQuarterSecondInterruptAsserted() {
	return quarterSecondInterrupt->isAsserted();
}

UINT32 TimingGenerator::getVerticalCount() {
	return verticalCountIndex;
}

UINT32 TimingGenerator::getHorizontalCount() {
	UINT32 horizontalCount = cpu->getCycleIndex() * HORIZ_NUM /
		cpu->getCycleNum();
	return horizontalCount ? (HORIZ_HPE - 1) + horizontalCount : 0;
}

void TimingGenerator::initScanlineIndexFromVerticalCount() {
	UINT32 verticalCount;
	for (verticalCount = verticalCountStart; verticalCount < VERT_END;
		 verticalCount++) {
		SINT32 scanlineIndex = verticalCount -
			(VERT_ACTIVEVIDEO - SCANLINE_APPLEII);
		if (scanlineIndex >= (VERT_END - verticalCountStart))
			scanlineIndex -= (VERT_END - verticalCountStart);
		
		if ((scanlineIndex < 0) || (scanlineIndex >= SCANLINE_END))
			scanlineIndex = ISVIDEOMODIFIED_INVISIBLE;
		
		scanlineIndexFromVerticalCount[verticalCount] = scanlineIndex;
	}
}

UINT32 TimingGenerator::getScanlineIndex() {
	return scanlineIndexFromVerticalCount[verticalCountIndex];
}

void TimingGenerator::onNextFrame() {
	oneSecondInterruptFrameIndex++;
	if (oneSecondInterruptFrameIndex >= oneSecondInterruptFrameNum) {
		oneSecondInterruptFrameIndex = 0;
		oneSecondInterrupt->assert();
	}
	
	quarterSecondInterruptFrameIndex++;
	if (quarterSecondInterruptFrameIndex >= QUARTERSEC_FRAMENUM) {
		quarterSecondInterruptFrameIndex = 0;
		quarterSecondInterrupt->assert();
	}
	
	vblInterrupt->assert();
	clearDisableCpuAccelerationForOneFrame();
}

void TimingGenerator::processFrame() {
	for (verticalCountIndex = verticalCountStart;
		 verticalCountIndex < VERT_VBL; verticalCountIndex++) {
		cpu->executeInstructions();
		frameClockIndex += CLOCK14M_SCANLINE;
		q3ClockIndex += HORIZ_NUM * 2;
		videoRenderer->onScanline(getScanlineIndex());
	}
	
	onNextFrame();
	videoRenderer->onNextFrame();
	
	for (; verticalCountIndex < VERT_END; verticalCountIndex++) {
		cpu->executeInstructions();
		frameClockIndex += CLOCK14M_SCANLINE;
		q3ClockIndex += HORIZ_NUM * 2;
		videoRenderer->onScanline(getScanlineIndex());
	}
	
	verticalCountIndex = VERT_END - 1;
	clockIndex += frameClockIndex;
	frameClockIndex = 0;
}
